#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <strings.h>

#include "common.h"
#include "tlb.h"

tlb_error
create_tlb(	TLB *t,
			sim_size slots) {
	t->nslots		= slots;
	t->entries		= 0;
	t->slots		= calloc(slots, sizeof(tlb_entry));
	
	if(!(t->slots))
		return E_SYS_NOMEM;

	return E_OKAY;
}

tlb_error
destroy_tlb(TLB *t) {
	if(t && t->slots)
		free(t->slots);

	return E_OKAY;
}

tlb_error
tlb_lookup(	TLB *t,
			tlb_vaddr vaddr,
			sim_size asid,
			tlb_paddr *paddr,
			tlb_paddr *flags) {
	sim_size index;
	tlb_vaddr vpn;
	tlb_paddr frame;
	tlb_error err;
	tlb_entry *ent;
	tlb_vaddr idx_mask;
	tlb_vaddr vpn_mask;

	// Turn the virtual address into a VPN with valid ASID
	vpn = (vaddr & TLB_VPN_MASK_4K) | (asid & VPN_ASID);

	if((err = tlb_search(t, vpn, &index)) != E_TLB_HIT)
		return err;

	ent			= t->slots+index;
	idx_mask	= TLB_IDX_MASK(ent->page_mask);
	
	if(vaddr & idx_mask)
		frame = ent->frame0;
	else
		frame = ent->frame1;
	
	if(flags)
		*flags = frame & (PFN_VALID | PFN_DIRTY | PFN_CACHE);
	
	vpn_mask = TLB_PFN_MASK(ent->page_mask);	
	if(paddr)
		*paddr = (frame & vpn_mask) | (vaddr & ~vpn_mask); 
	
	return E_TLB_HIT;
}

tlb_error
tlb_search(	TLB *t,
			tlb_vaddr vaddr,
			sim_size *index) {
	sim_size idx;
	tlb_entry *ent;
	tlb_vaddr vpn_mask;
	tlb_vaddr vpn_asid;
	tlb_vaddr ent_vpn;
	tlb_vaddr src_vpn;
	tlb_vaddr asid;

	asid = GET_ASID(vaddr);

	for(idx=0; idx < t->nslots; idx++) {
		ent			= t->slots+idx;
		vpn_mask	= TLB_VPN_MASK(ent->page_mask);
		vpn_asid	= GET_ASID(ent->vpn);
		ent_vpn		= ent->vpn & vpn_mask;
		src_vpn		= vaddr & vpn_mask;

		if((ent_vpn == src_vpn) && (vpn_asid == asid || ent->global)) {
			*index = idx;
			return E_TLB_HIT;
		}
	}

	return E_TLB_MISS;
}

tlb_error
tlb_set(	TLB *t,
			sim_size index,
			tlb_vaddr vpn,
			tlb_vaddr mask,
			tlb_paddr frame0,
			tlb_paddr frame1) {
	tlb_entry *ent;

	if(index >= t->nslots)
		return E_INVALID;

	ent				= t->slots+index;
	ent->vpn		= vpn;
	ent->page_mask	= mask;

	// Ensure that there is only one global bit...
	// perhaps there should be a warning raised if (frame0 & PFN_GLOBAL) != (frame1 & PFN_GLOBAL)
	ent->global = (frame0 & PFN_GLOBAL) | (frame1 & PFN_GLOBAL);
	ent->frame0	= (frame0 & ~PFN_GLOBAL) | ent->global;
	ent->frame1 = (frame1 & ~PFN_GLOBAL) | ent->global;

	return E_OKAY;
}

tlb_error
tlb_get(	TLB *t,
			sim_size index,
			tlb_vaddr *vpn,
			tlb_vaddr *mask,
			tlb_paddr *frame0,
			tlb_paddr *frame1) {
	tlb_entry *ent;
	
	if(index >= t->nslots)
		return E_INVALID;

	ent	= t->slots+index;
	
	if(vpn)		*vpn = ent->vpn;
	if(mask)	*mask = ent->page_mask;
	if(frame0)	*frame0 = ent->frame0;
	if(frame1)	*frame1 = ent->frame1;

	return E_OKAY;
}

